#ifndef _Reservation_Station
#define _Reservation_Station
#include <iostream>
#include <cstring>
#include <cstdio>
#include "store.hpp"
#include "arithmetic.hpp"
#include "tomasulo_cpu.hpp"

bool CPU::RS_ready(const CPU::Reservation_Station::node &t)
{
    if (t.opcode == 55 || t.opcode == 23 || t.opcode == 111) // LUI AUIPC JAL 
        return 1;

    if (~t.Qj) return 0;

    if ((t.opcode == 99 || t.opcode == 51) && ~t.Qk) // BEQ ~ BGEU && ADD ~ AND
        return 0;

    return 1;
}

int CPU::RS_find()
{
    if (!CPU::RS_prev.h.empty())
    {
        for (int i = 0; i < CPU::queue_len; i++)
            if (CPU::RS_prev.h.busy[i] && CPU::RS_ready(CPU::RS_prev.h.node[i]))
                return i;
    }

    return -1;
}

void CPU::RS_run()
{
    #ifdef func
        puts("");
        puts("---RS_run---");
    #endif 
    CPU::RS_new = CPU::RS_prev;

    if (CPU::commit_signal < 0)
    {
        CPU::RS_new.h.clear();
        return;
    }

    CPU::Reservation_Station::node t = CPU::issue_to_RS;

    CPU::CDB c = CPU::ex_to_CDB;
    if (~c.dest)
    {
        for (int i = 0; i < CPU::queue_len; i++)
            if (CPU::RS_prev.h.busy[i])
            {
                if (CPU::RS_prev.h.node[i].Qj == c.dest)
                {
                    CPU::RS_new.h.node[i].Vj = c.val;
                    CPU::RS_new.h.node[i].Qj = -1;
                }

                if (CPU::RS_prev.h.node[i].Qk == c.dest)
                {
                    CPU::RS_new.h.node[i].Vk = c.val;
                    CPU::RS_new.h.node[i].Qk = -1;
                }
            }

        if (t.Qj == c.dest)
        {
            t.Vj = c.val;
            t.Qj = -1;
        }
        if (t.Qk == c.dest)
        {
            t.Vk = c.val;
            t.Qk = -1;
        }
    }

    c = CPU::LSB_to_CDB_prev;
    if (~c.dest)
    {
        for (int i = 0; i < CPU::queue_len; i++)
            if (CPU::RS_prev.h.busy[i])
            {
                if (CPU::RS_prev.h.node[i].Qj == c.dest)
                {
                    CPU::RS_new.h.node[i].Vj = c.val;
                    CPU::RS_new.h.node[i].Qj = -1;
                }

                if (CPU::RS_prev.h.node[i].Qk == c.dest)
                {
                    CPU::RS_new.h.node[i].Vk = c.val;
                    CPU::RS_new.h.node[i].Qk = -1;
                }
            }

        if (t.Qj == c.dest)
        {
            t.Vj = c.val;
            t.Qj = -1;
        }
        if (t.Qk == c.dest)
        {
            t.Vk = c.val;
            t.Qk = -1;
        }
    }

    if (~t.opcode)
        CPU::RS_new.h.push(t);

    #ifdef func
        puts("------------");
        puts("");
    #endif 

    int pos = CPU::RS_find();
    if (pos == -1) return;
    CPU::RS_to_ex = RS_new.h.node[pos];
    CPU::RS_new.h.pop(pos);
}

void CPU::RS_ex()
{
    #ifdef func
        puts("");
        puts("---RS_ex---");
    #endif 
    CPU::Reservation_Station::node &t = CPU::RS_to_ex;

    if (t.opcode == -1) return;

    if (t.opcode == 55) // LUI
    {
        #ifdef debug
            printf("lui %d %d\n", t.rd, t.imm);
        #endif

        CPU::ex_to_CDB = CPU::CDB(t.dest, t.A);
    }
    else if (t.opcode == 23) // AUIPC
    {
        #ifdef debug
            printf("auipc %d %d\n", t.rd, t.imm);
        #endif

        CPU::ex_to_CDB = CPU::CDB(t.dest, t.pc + t.A);
    }
    else if (t.opcode == 111) // JAL
    {
        #ifdef debug
            printf("jal %d %d\n", t.rd, t.imm);
        #endif

        CPU::ex_to_CDB = CPU::CDB(t.dest, t.pc + 4, t.pc + t.A, t.A != 4);
    }
    else if (t.opcode == 103) // JALR
    {
        #ifdef debug
            printf("jalr %d %d\n", t.rd, t.imm);
        #endif

        CPU::ex_to_CDB = CPU::CDB(t.dest, t.pc + 4, (t.Vj + t.A) & ~1, ((t.Vj + t.A) & ~1) - t.pc != 4);
    }
    else if (t.opcode == 99) // BEQ ~ BGEU
    {
        if (t.func3 == 0) // BEQ
        {
            #ifdef debug
                printf("beq %d %d\n", t.rd, t.imm);
            #endif

            if (t.Vj == t.Vk)
            {
                CPU::ex_to_CDB = CPU::CDB(t.dest, 0, t.pc + t.A, t.A != 4);
            }
            else
            {
                CPU::ex_to_CDB = CPU::CDB(t.dest, 0);
            }
        }
        else if (t.func3 == 1) // BNE
        {
            #ifdef debug
                printf("bne %d %d %d\n", t.rd, t.rs1_val, t.rs2_val);
            #endif

            if (t.Vj != t.Vk)
            {
                CPU::ex_to_CDB = CPU::CDB(t.dest, 0, t.pc + t.A, t.A != 4);
            }
            else
            {
                CPU::ex_to_CDB = CPU::CDB(t.dest, 0);
            }
        }
        else if (t.func3 == 4) // BLT
        {
            #ifdef debug
                printf("blt %d %d\n", t.rd, t.imm);
            #endif

            if (static_cast<int>(t.Vj) < static_cast<int>(t.Vk))
            {
                CPU::ex_to_CDB = CPU::CDB(t.dest, 0, t.pc + t.A, t.A != 4);
            }
            else
            {
                CPU::ex_to_CDB = CPU::CDB(t.dest, 0);
            }
        }
        else if (t.func3 == 5) // BEG
        {
            #ifdef debug
                printf("beg %d %d\n", t.rd, t.imm);
            #endif

            if (static_cast<int>(t.Vj) >= static_cast<int>(t.Vk))
            {
                CPU::ex_to_CDB = CPU::CDB(t.dest, 0, t.pc + t.A, t.A != 4);
            }
            else
            {
                CPU::ex_to_CDB = CPU::CDB(t.dest, 0);
            }
        }
        else if (t.func3 == 6) //BLTU
        {
            #ifdef debug
                printf("bltu %d %d\n", t.rd, t.imm);
            #endif

            if (t.Vj < t.Vk)
            {
                CPU::ex_to_CDB = CPU::CDB(t.dest, 0, t.pc + t.A, t.A != 4);
            }
            else
            {
                CPU::ex_to_CDB = CPU::CDB(t.dest, 0);
            }
        }
        else if (t.func3 == 7) // BGEU
        {
            #ifdef debug
                printf("bgeu %d %d %d\n", t.rd, t.rs1_val, t.rs2_val);
            #endif

            if (t.Vj >= t.Vk)
            {
                CPU::ex_to_CDB = CPU::CDB(t.dest, 0, t.pc + t.A, t.A != 4);
            }
            else
            {
                CPU::ex_to_CDB = CPU::CDB(t.dest, 0);
            }
        }
    }
    else if (t.opcode == 19) // ADDI ~ SRAI
    {
        if (t.func3 == 0) // ADDI
        {
            #ifdef debug
                printf("addi %d %d %d\n", t.rd, t.rs1_val, t.imm);
            #endif

            CPU::ex_to_CDB = CPU::CDB(t.dest, t.Vj + t.A);
        }
        else if (t.func3 == 2) // SLTI
        {
            #ifdef debug
                printf("slti %d %d\n", t.rd, t.imm);
            #endif

            CPU::ex_to_CDB = CPU::CDB(t.dest, static_cast<int>(t.Vj) < static_cast<int>(t.A));
        }
        else if (t.func3 == 3) // STLIU
        {
            #ifdef debug
                printf("stliu %d %d\n", t.rd, t.imm);
            #endif

            CPU::ex_to_CDB = CPU::CDB(t.dest, t.Vj < t.A);
        }
        else if (t.func3 == 4) // XORI
        {
            #ifdef debug
                printf("xori %d %d\n", t.rd, t.imm);
            #endif

            CPU::ex_to_CDB = CPU::CDB(t.dest, t.Vj ^ t.A);
        }
        else if (t.func3 == 6) // ORI
        {
            #ifdef debug
                printf("ori %d %d\n", t.rd, t.imm);
            #endif

            CPU::ex_to_CDB = CPU::CDB(t.dest, t.Vj | t.A);
        }
        else if (t.func3 == 7) // ANDI
        {
            #ifdef debug
                printf("andi %d %d\n", t.rd, t.imm);
            #endif

            CPU::ex_to_CDB = CPU::CDB(t.dest, t.Vj & t.A);
        }
        else if (t.func3 == 1) // SLLI
        {
            #ifdef debug
                printf("slli %d %d\n", t.rd, t.imm);
            #endif

            CPU::ex_to_CDB = CPU::CDB(t.dest, t.Vj << t.A);
        }
        else if (t.func3 == 5) // SRLI ~ SRAI
        {
            if (t.func7 == 0) // SRLI
            {
                #ifdef debug
                    printf("srli %d %d\n", t.rd, t.imm);
                #endif

                CPU::ex_to_CDB = CPU::CDB(t.dest, t.Vj >> t.A);
            }
            else if (t.func7 == 32) // SRAI
            {
                #ifdef debug
                    printf("srai %d %d\n", t.rd, t.imm);
                #endif

                CPU::ex_to_CDB = CPU::CDB(t.dest, sext(t.Vj >> t.A, 31 - t.A));
            }
        }
    }
    else if (t.opcode == 51) // ADD ~ AND
    {
        if (t.func3 == 0) // ADD ~ SUB
        {
            if (t.func7 == 0) // ADD
            {
                #ifdef debug
                    printf("add %d %d\n", t.rd, t.imm);
                #endif

                CPU::ex_to_CDB = CPU::CDB(t.dest, t.Vj + t.Vk);
            }
            else if (t.func7 == 32) // SUB
            {
                #ifdef debug
                    printf("sub %d %d\n", t.rd, t.imm);
                #endif

                CPU::ex_to_CDB = CPU::CDB(t.dest, t.Vj - t.Vk);
            }
        }
        else if (t.func3 == 1) // SLL
        {
            #ifdef debug
                printf("sll %d %d\n", t.rd, t.imm);
            #endif

            CPU::ex_to_CDB = CPU::CDB(t.dest, t.Vj << (t.Vk & ((1 << 5) - 1)));
        }
        else if (t.func3 == 2) // SLT
        {
            #ifdef debug
                printf("slt %d %d\n", t.rd, t.imm);
            #endif

            CPU::ex_to_CDB = CPU::CDB(t.dest, static_cast<int>(t.Vj) < static_cast<int>(t.Vk));
        }
        else if (t.func3 == 3) // SLTU
        {
            #ifdef debug
                printf("sltu %d %d\n", t.rd, t.imm);
            #endif

            CPU::ex_to_CDB = CPU::CDB(t.dest, t.Vj < t.Vk);
        }
        else if (t.func3 == 4) // XOR
        {
            #ifdef debug
                printf("xor %d %d %d\n", t.rd, t.rs1_val, t.rs2_val);
            #endif

            // printf("XOR t.Vj:%u t.Vk:%u\n", t.Vj, t.Vk);

            CPU::ex_to_CDB = CPU::CDB(t.dest, t.Vj ^ t.Vk);
        }
        else if (t.func3 == 5) // SRL ~ SRA
        {
            if (t.func7 == 0) // SRL
            {
                #ifdef debug
                    printf("srl %d %d\n", t.rd, t.imm);
                #endif

                CPU::ex_to_CDB = CPU::CDB(t.dest, t.Vj >> (t.Vk & ((1 << 5) - 1)));
            }
            else if (t.func7 == 32) // SRA
            {
                #ifdef debug
                    printf("sra %d %d\n", t.rd, t.imm);
                #endif

                CPU::ex_to_CDB = CPU::CDB(t.dest, sext(t.Vj >> (t.Vk & ((1 << 5) - 1)), 31 - (t.Vk & ((1 << 5) - 1))));
            }
        }
        else if (t.func3 == 6) // OR
        {
            #ifdef debug
                printf("or %d %d\n", t.rd, t.imm);
            #endif

            CPU::ex_to_CDB = CPU::CDB(t.dest, t.Vj | t.Vk);
        }
        else if (t.func3 == 7) // AND
        {
            #ifdef debug
                printf("and %d %d\n", t.rd, t.imm);
            #endif

            CPU::ex_to_CDB = CPU::CDB(t.dest, t.Vj & t.Vk);
        }
    }

    t.opcode = -1;

    #ifdef func
        puts("-----------");
        puts("");
    #endif 
}

#endif